﻿#pragma once

#include "ff_utils.h"

#include <CL/cl.h>

#pragma comment(lib,"OpenCL.lib")

namespace feifei
{
#define WAVE_SIZE			(32)
#define MAX_GRP_SIZE		(1024)
#define OCL_ASSERT(x)		(assert((x)==CL_SUCCESS))

	typedef enum class MemTypeEnum
	{
		Page = 1
	}E_MemType; 
	typedef struct dim3
	{
		uint32_t x, y, z;
		dim3(uint32_t vx = 1, uint32_t vy = 1, uint32_t vz = 1) : x(vx), y(vy), z(vz) {}
		size_t* arr() { return new size_t[3]{ x,y,z }; }

		dim3 operator/(dim3 b)
		{
			uint32_t x2 = this->x / b.x;
			uint32_t y2 = this->y / b.y;
			uint32_t z2 = this->z / b.z;
			dim3 c(x2, y2, z2);
			return c;
		}
		dim3 operator*(dim3 b)
		{
			uint32_t x2 = this->x * b.x;
			uint32_t y2 = this->y * b.y;
			uint32_t z2 = this->z * b.z;
			dim3 c(x2, y2, z2);
			return c;
		}
	}dim3;

	extern E_ffState InitGpuRuntime();
	extern void ReleaseGpuRuntime();
	extern void PrintGpuRuntimeInfo(bool isFullInfo = false);
	extern void BandwidthTest();

	extern void* DevMalloc(size_t byteNum);
	extern void DevFree(void* d_mem);
	extern void* HstMalloc(size_t byteNum, E_MemType memType = E_MemType::Page);
	extern E_ffState MemCopyH2D(void* d_mem, void* h_mem, size_t byteNum);
	extern E_ffState MemCopyD2H(void* h_mem, void* d_mem, size_t byteNum);
	extern E_ffState MemCopyD2D(void* d_dst, void* d_src, size_t byteNum);

	extern std::string get_kernel_path();
	class GpuKernel
	{
	protected:
#define MAX_ARGS_SIZE		1024
		std::string compiler;
		std::string buildOption = "";

		std::string kernelName;		// kernel函数名字
		std::string sourceFile;	// kernel源程序文件(含路径)
		char* sourceString;		// kernel源程序字符串

		cl_program clProgram;
		cl_kernel clKernel;

		uint32_t argsCnt = 0;
		E_ffState setArgs() { return E_ffState::SUCCESS; }
		template <typename T, typename... Ts> E_ffState setArgs(T head, Ts... rest)
		{
			//unsigned int argsCnt = sizeof...(rest);
			T param = head;

			cl_int errNum;
			errNum = clSetKernelArg(clKernel, argsCnt, sizeof(T), &param);
			argsCnt++;
			setArgs(rest...);
			if (errNum != CL_SUCCESS)
			{
				ERR("Error setting kernel arguments.");
				E_ffState::RTN_ERR;
			}
			return E_ffState::SUCCESS;
		}

		dim3 GroupSize;
		dim3 GroupNum;
		dim3 GlobalSize;

		E_ffState creatKernelFromCppFile();
		E_ffState creatKernelFromCppString();

	public:
		GpuKernel(std::string kernelName, std::string srcFile = "");
		~GpuKernel()
		{
			 if (clKernel != 0)		clReleaseKernel(clKernel);
			 if (clProgram != 0)	clReleaseProgram(clProgram);
		}

		template <typename... Ts> E_ffState SetArgs(Ts... param)
		{
			argsCnt = 0;
			setArgs(param...);
			return E_ffState::SUCCESS;
		}

		void SetGroupSize(uint32_t x, uint32_t y = 1, uint32_t z = 1)
		{
			GroupSize.x = x;
			GroupSize.y = y;
			GroupSize.z = z;
		}
		void SetGroupNum(uint32_t x, uint32_t y = 1, uint32_t z = 1)
		{
			GroupNum.x = x;
			GroupNum.y = y;
			GroupNum.z = z;

			GlobalSize.x = GroupSize.x * GroupNum.x;
			GlobalSize.y = GroupSize.y * GroupNum.y;
			GlobalSize.z = GroupSize.z * GroupNum.z;
		}
		void SetGlobalSize(uint32_t x, uint32_t y = 1, uint32_t z = 1)
		{
			GlobalSize.x = x;
			GlobalSize.y = y;
			GlobalSize.z = z;

			GroupNum.x = (GlobalSize.x + GroupSize.x - 1) / GroupSize.x;
			GroupNum.y = (GlobalSize.y + GroupSize.y - 1) / GroupSize.y;
			GroupNum.z = (GlobalSize.z + GroupSize.z - 1) / GroupSize.z;

			SetGroupNum(GroupNum.x, GroupNum.y, GroupNum.z); return;
		}
		void PrintWorkLoad()
		{
			LOG("WorkLoad:");
			INFO("\t- group  size = [%d, %d, %d]", GroupSize.x, GroupSize.y, GroupSize.z);
			INFO("\t- group  num  = [%d, %d, %d]", GroupNum.x, GroupNum.y, GroupNum.z);
			INFO("\t- global size = [%d, %d, %d]", GlobalSize.x, GlobalSize.y, GlobalSize.z);
		}

		E_ffState Launch();
	};

	/// <summary>
	/// 打印输出GPU数组
	/// </summary>
	/// <typeparam name="T"></typeparam>
	/// <param name="pData">数组地址</param>
	/// <param name="len">输出元素的个数</param>
	/// <param name="name">输出数据的名字(默认为空)</param>
	/// <param name="startIdx">起始下标(默认为0)</param>
	/// <param name="isHex">是否以十六进制输出(默认为否)</param>
	/// <param name="numPerRow">每行输出的元素个数(默认为8个)</param>
	/// <param name="fmtLen">输出格式(默认为2位)</param>
	/// <param name="of">输出流(默认为标准输出)</param>
	template<typename T> void LogDataDev(const T* d_addr, const size_t len,
		std::string name = "",
		uint64_t startIdx = 0, bool isHex = false,
		int numPerRow = 8, int fmtLen = 2,
		FILE* of = stdout)
	{
		if (d_addr == NULL)
		{
			WARN("null data! \r\n");
			return;
		}

		size_t copy_len = startIdx + len;

		T* h_data = (T*)malloc(copy_len * sizeof(T));
		MemCopyD2H((void*)h_data, (void*)d_addr, copy_len * sizeof(T));

		LogData<T>(h_data, len, name, startIdx, isHex, numPerRow, fmtLen, of);
		free(h_data);
	}
	/// <summary>
	/// 以ASCII格式打印GPU数据到文件
	/// </summary>
	/// <typeparam name="T"></typeparam>
	/// <param name="pData">数据地址</param>
	/// <param name="len">打印元素的个数</param>
	/// <param name="file_name">文件名(需要文件全路径和拓展名)</param>
	/// <param name="isAppend">是否追加文件(默认不追加，创建新文件)</param>
	/// <param name="startIdx">起始下表(默认为0)</param>
	/// <param name="isHex">是否以十六进制格式输出(默认为否)</param>
	/// <param name="numPerRow">每行输出的元素个数(默认为8个)</param>
	/// <param name="fmtLen">输出格式(默认为4位)</param>
	template<typename T> void DumpDataDev(const T* d_addr, const size_t len,
		std::string full_file_name, bool isAppend = false,
		uint64_t startIdx = 0, bool isHex = false,
		int numPerRow = 8, int fmtLen = 4)
	{
		FILE* fp;
		if (isAppend)	fp = fopen(full_file_name.c_str(), "a+");
		else 			fp = fopen(full_file_name.c_str(), "w+");
		LogDataDev<T>(d_addr, len, "", startIdx, isHex, numPerRow, fmtLen, fp);
		fclose(fp);
	}
	/// <summary>
	/// 以二进制个数保存GPU数据到文件
	/// </summary>
	/// <typeparam name="T"></typeparam>
	/// <param name="addr">数据地址</param>
	/// <param name="len">保存元素的个数</param>
	/// <param name="file_name">文件名(需要文件全路径和拓展名)</param>
	/// <param name="isAppend">是否追加文件(默认不追加，创建新文件)</param>
	/// <param name="startIdx">起始下表(默认为0)</param>
	template<typename T> void DumpMemDev(const T* d_addr, const size_t len,
		std::string data_name, bool isAppend = false,
		uint64_t startIdx = 0)
	{
		if (d_addr == NULL)
		{
			WARN("null data! \r\n");
			return;
		}

		size_t copy_len = startIdx + len;
		T* h_data = (T*)malloc(copy_len * sizeof(T));
		MemCopyD2H(h_data, d_addr, copy_len * sizeof(T));

		DumpMem<T>(h_data, len, data_name, isAppend, startIdx);
		free(h_data);
	}
	/// <summary>
	/// 从二进制文件读取数据到GPU
	/// </summary>
	/// <param name="addr">读取到的地址,如果希望该函数分配,需要将 *addr = NULL !!!!! </param>
	/// <param name="file_name">文件名(需要文件全路径和拓展名)</param>
	/// <param name="skip_size">跳过的字节数</param>
	/// <param name="load_size">读取的字节数</param>
	/// <returns>返回读取的字节数</returns>
	static size_t LoadMemDev(void** d_addr, std::string file_name, size_t skip_size = 0, size_t load_size = 0)
	{
		void* hst_addr = nullptr;

		size_t rd_sz = LoadMem((void**)&hst_addr, file_name, skip_size, load_size);

		if (*d_addr == NULL)
			*d_addr = DevMalloc(rd_sz);
		MemCopyH2D(*d_addr, hst_addr, rd_sz);

		free(hst_addr);

		return rd_sz;
	}
} // end namespace feifei
